﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;

namespace DimMock
{
    public static class Mock
    {
        /// <summary>
        /// Mocks a new object with optional random values.
        /// </summary>
        /// <typeparam name="TargetType">The type of the object to mock.</typeparam>
        /// <param name="mockProperties">if set to <c>true</c> properties on the new object will be mocked as well.</param>
        /// <param name="allowNullValues">if set to <c>true</c> will allow null values to be mocked for the object's properties.  This value is ignored if <paramref name="mockProperties"/> is set to <c>false</c>.</param>
        /// <returns>A new mock object.</returns>
        public static TargetType Create<TargetType>(bool mockProperties = true, bool allowNullValues = false)
        {
            TargetType mock;
            ConstructorInfo mockConstructor = typeof(TargetType).GetConstructor(new Type[] { });
            if (mockConstructor == null)
            {
                try
                {
                    mock = default(TargetType);

                    if (mock == null)
                        mock = Mockers.MockValue(typeof(TargetType));
                }
                catch (Exception ex)
                {
                    // Just a cleaner way to handle
                    throw new MockInitializationException("Could not initialize a new object of type \"{0}\"", ex, typeof(TargetType).Name);
                }
            }
            else
                mock = (TargetType)mockConstructor.Invoke(new Object[] { });

            if (mock == null)
                throw new UnableToMockObjectException();

            if (mockProperties)
                mock = mock.Mock<TargetType>(allowNullValues);

            return mock;
        }

        /// <summary>
        /// Creates the specified target type.
        /// </summary>
        /// <param name="targetType">Type of the target.</param>
        /// <param name="mockProperties">if set to <c>true</c> [mock properties].</param>
        /// <param name="allowNullValues">if set to <c>true</c> [allow null values].</param>
        /// <returns></returns>
        public static dynamic Create(Type targetType, bool mockProperties = true, bool allowNullValues = false)
        {
            if (targetType == null)
                throw new ArgumentNullException("targetType");

            dynamic mock = null;
            ConstructorInfo mockConstructor = targetType.GetConstructor(new Type[] { });
            if (mockConstructor == null)
            {
                try
                {
                    if (mock == null)
                        mock = Mockers.MockValue(targetType);
                }
                catch (Exception ex)
                {
                    // Just a cleaner way to handle
                    throw new MockInitializationException("Could not initialize a new object of type \"{0}\"", ex, targetType.Name);
                }
            }
            else
                mock = mockConstructor.Invoke(new Object[] { });

            if (mock == null)
                throw new UnableToMockObjectException();

            mock = Mockers.MockClass(mock, allowNullValues);

            return mock;
        }

        /// <summary>
        /// Mocks a new list of objects with optional random values.
        /// </summary>
        /// <typeparam name="TargetType">The type of the arget type.</typeparam>
        /// <param name="min">The min.</param>
        /// <param name="max">The max.</param>
        /// <param name="mockProperties">if set to <c>true</c> [mock properties].</param>
        /// <param name="allowNullValues">if set to <c>true</c> [allow null values].</param>
        /// <returns></returns>
        public static List<TargetType> Create<TargetType>(uint min, uint max, bool mockProperties = true, bool allowNullValues = false)
        {
            if ((min == 0 & max <= min) || max < min)
                throw new ArgumentException("The value of max must be greater than min and greater than or equal to 1.");

            List<TargetType> mocks = new List<TargetType>();
            
            int objectCount = Random.Int((int)min, (int)max);
            int i = 0;

            do
            {
                mocks.Add(Mock.Create<TargetType>(mockProperties, allowNullValues));
                i++;
            } while(i < objectCount);
            
            return mocks;
        }
    }
}
